﻿/* 
This file is part of Ingo Karstein's Long Path Support project

**Do not remove this comment**

Please see the project homepage at CodePlex:
  http://longpathsupport.codeplex.com/

Please see my blog:
  http://ikarstein.wordpress.com

This code is used in the project "RoboPowerCopy"
  http://robopowercopy.codeplex.com

Licence: GPLv2
    Ingo Karstein's SharePoint 2010 Custom Ribbon Demo
    Copyright (C) 2011  Ingo Karstein, ikarstein@hotmail.com

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
using System.Reflection;
using System.Security;
using System.Security.Permissions;
using System.Runtime;
using System.Security.AccessControl;
using System.Diagnostics;


public static class LongPathSupport
{
    // Adapted from diffrent sources, e.g.
    //   http://blogs.msdn.com/b/bclteam/archive/2007/03/26/long-paths-in-net-part-2-of-3-long-path-workarounds-kim-hamilton.aspx
    //   http://support.microsoft.com/kb/175512

    internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
    internal static int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
    internal const int MaxPath = 0x7d00;

    [StructLayout(LayoutKind.Sequential)]
    internal struct FILETIME
    {
        internal uint dwLowDateTime;
        internal uint dwHighDateTime;
    };

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    internal struct WIN32_FIND_DATA
    {
        internal WIN32_FILEATTRIBUTES dwFileAttributes;
        internal FILETIME ftCreationTime;
        internal FILETIME ftLastAccessTime;
        internal FILETIME ftLastWriteTime;
        internal int nFileSizeHigh;
        internal int nFileSizeLow;
        internal int dwReserved0;
        internal int dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MaxPath)]
        internal string cFileName;
        // not using this
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        internal string cAlternate;
    }

    [Flags]
    internal enum WIN32_FILEACCESS : uint
    {
        GenericRead = 0x80000000,
        GenericWrite = 0x40000000,
        GenericExecute = 0x20000000,
        GenericAll = 0x10000000,
    }

    [Flags]
    internal enum WIN32_FILESHARE : uint
    {
        None = 0x00000000,
        Read = 0x00000001,
        Write = 0x00000002,
        Delete = 0x00000004,
    }

    internal enum WIN32_CREATIONDISPOSITION : uint
    {
        New = 1,
        CreateAlways = 2,
        OpenExisting = 3,
        OpenAlways = 4,
        TruncateExisting = 5,
    }

    [Flags]
    internal enum WIN32_FILEATTRIBUTES : uint
    {
        Readonly = 0x00000001,
        Hidden = 0x00000002,
        System = 0x00000004,
        Directory = 0x00000010,
        Archive = 0x00000020,
        Device = 0x00000040,
        Normal = 0x00000080,
        Temporary = 0x00000100,
        SparseFile = 0x00000200,
        ReparsePoint = 0x00000400,
        Compressed = 0x00000800,
        Offline = 0x00001000,
        NotContentIndexed = 0x00002000,
        Encrypted = 0x00004000,
        Write_Through = 0x80000000,
        Overlapped = 0x40000000,
        NoBuffering = 0x20000000,
        RandomAccess = 0x10000000,
        SequentialScan = 0x08000000,
        DeleteOnClose = 0x04000000,
        BackupSemantics = 0x02000000,
        PosixSemantics = 0x01000000,
        OpenReparsePoint = 0x00200000,
        OpenNoRecall = 0x00100000,
        FirstPipeInstance = 0x00080000
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct SECURITY_ATTRIBUTES
    {
        public int nLength;
        public IntPtr lpSecurityDescriptor;
        public int bInheritHandle;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, EntryPoint = "DeleteFile", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool RemoveFile(string lpFileName);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern SafeFileHandle CreateFile(
        string lpFileName,
        WIN32_FILEACCESS dwDesiredAccess,
        WIN32_FILESHARE dwShareMode,
        IntPtr lpSecurityAttributes,
        WIN32_CREATIONDISPOSITION dwCreationDisposition,
        WIN32_FILEATTRIBUTES dwFlagsAndAttributes,
        IntPtr hTemplateFile);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr FindFirstFile(string lpFileName, out
		                                                                                WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool FindNextFile(IntPtr hFindFile, out
		                                                                                WIN32_FIND_DATA lpFindFileData);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool FindClose(IntPtr hFindFile);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CreateDirectory(string lpPathName,
       IntPtr lpSecurityAttributes);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool RemoveDirectory(string lpPathName);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool SetFileAttributes(string lpFileName, uint dwFileAttributes);

    [Flags]
    internal enum FindType
    {
        Files = 0x1,
        Directories = 0x2
    }

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.U4)]
    private static extern int GetLongPathName(
        [MarshalAs(UnmanagedType.LPTStr)]
                        string lpszShortPath,
                [MarshalAs(UnmanagedType.LPTStr)]
                        StringBuilder lpszLongPath,
                [MarshalAs(UnmanagedType.U4)]
                        int cchBuffer);


    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    static extern uint GetFullPathName(string lpFileName, uint nBufferLength,
       [Out] StringBuilder lpBuffer, [Out] StringBuilder lpFilePart);

    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true, EntryPoint = "EncryptFile")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool W32EncryptFile(string filename);

    [DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "DecryptFile")]
    static extern bool W32DecryptFile(string lpFileName, uint dwReserved);

    public static void DeleteFile(string fileName)
    {
        if( !SetFileAttributes(fileName, 0) )
            throw new Exception(LastError());
        if( !RemoveFile(fileName) )
            throw new Exception(LastError());
    }

    public static void DeleteDirectory(string dirName)
    {
        DeleteDirectory(dirName, false);
    }

    public static void DeleteDirectory(string dirName, bool recursive)
    {
        if( recursive )
        {
            new LongDirectoryInfo(dirName).Delete(true);
            return;
        }

        if( DirectoryExists(dirName) )
            if( !RemoveDirectory(dirName) )
                throw new Exception(LastError());
    }

    public static string Combine(string path1, string path2)
    {
        if( ( path1 == null ) || ( path2 == null ) )
        {
            throw new ArgumentException("Arguments invalid");
        }

        if( !CheckInvalidPathChars(path1) || !CheckInvalidPathChars(path2) )
            throw new ArgumentException("Invalid Characters in path");

        if( path2.Length == 0 )
        {
            return path1;
        }
        if( path1.Length == 0 )
        {
            return path2;
        }
        if( IsPathRooted(path2) )
        {
            return path2;
        }
        char ch = path1[path1.Length - 1];
        if( ( ( ch != System.IO.Path.DirectorySeparatorChar ) && ( ch != System.IO.Path.AltDirectorySeparatorChar ) ) && ( ch != System.IO.Path.VolumeSeparatorChar ) )
        {
            return ( path1 + System.IO.Path.DirectorySeparatorChar + path2 );
        }
        return ( path1 + path2 );
    }

    public static bool IsPathRooted(string path)
    {
        if( path != null )
        {
            if( !CheckInvalidPathChars(path) )
                throw new ArgumentException("Path contains invalid characters.");
            int length = path.Length;
            if( ( ( length >= 1 ) && ( ( path[0] == System.IO.Path.DirectorySeparatorChar ) || ( path[0] == System.IO.Path.AltDirectorySeparatorChar ) ) ) || ( ( length >= 2 ) && ( path[1] == System.IO.Path.VolumeSeparatorChar ) ) )
            {
                return true;
            }
        }
        return false;
    }

    public static bool CreateDirectory(string directory)
    {
        return CreateDirectory(directory, IntPtr.Zero);
    }

    public static System.IO.FileStream Open(string path, System.IO.FileAccess access, System.IO.FileMode mode, System.IO.FileShare share)
    {
        WIN32_FILEACCESS fa = WIN32_FILEACCESS.GenericAll;
        switch( access )
        {
            case System.IO.FileAccess.Read:
                fa = WIN32_FILEACCESS.GenericRead;
                break;
            case System.IO.FileAccess.Write:
                fa = WIN32_FILEACCESS.GenericWrite;
                break;
            case System.IO.FileAccess.ReadWrite:
                fa = WIN32_FILEACCESS.GenericRead | WIN32_FILEACCESS.GenericWrite;
                break;
        }

        WIN32_CREATIONDISPOSITION cd = WIN32_CREATIONDISPOSITION.CreateAlways;
        switch( mode )
        {
            case System.IO.FileMode.Append:
                cd = WIN32_CREATIONDISPOSITION.OpenExisting;
                break;
            case System.IO.FileMode.Create:
                cd = WIN32_CREATIONDISPOSITION.New;
                break;
            case System.IO.FileMode.CreateNew:
                cd = WIN32_CREATIONDISPOSITION.CreateAlways;
                break;
            case System.IO.FileMode.Open:
                cd = WIN32_CREATIONDISPOSITION.OpenExisting;
                break;
            case System.IO.FileMode.OpenOrCreate:
                cd = WIN32_CREATIONDISPOSITION.OpenAlways;
                break;
            case System.IO.FileMode.Truncate:
                cd = WIN32_CREATIONDISPOSITION.TruncateExisting;
                break;
        }

        WIN32_FILESHARE fs = WIN32_FILESHARE.None;
        switch( share )
        {
            case System.IO.FileShare.Delete:
                fs = WIN32_FILESHARE.Delete;
                break;
            case System.IO.FileShare.Inheritable:
                fs = WIN32_FILESHARE.None;
                break;
            case System.IO.FileShare.None:
                fs = WIN32_FILESHARE.None;
                break;
            case System.IO.FileShare.Read:
                fs = WIN32_FILESHARE.Read;
                break;
            case System.IO.FileShare.Write:
                fs = WIN32_FILESHARE.Write;
                break;
            case System.IO.FileShare.ReadWrite:
                fs = WIN32_FILESHARE.Read | WIN32_FILESHARE.Write;
                break;
        }

        SafeFileHandle sfh = CreateFile(path, fa, fs, IntPtr.Zero, cd, 0, IntPtr.Zero);

        if( !sfh.IsClosed && !sfh.IsInvalid )
        {
            System.IO.FileStream stream = new System.IO.FileStream(sfh, access);
            if( mode == System.IO.FileMode.Append )
            {
                stream.Seek(0, System.IO.SeekOrigin.End);
            }
            return stream;
        }

        return null;
    }



    internal static List<string> FindFilesAndDirs(string dirName, FindType types, bool recursive)
    {
        List<string> results = new List<string>();
        List<string> subResults = new List<string>();
        WIN32_FIND_DATA findData;
        IntPtr findHandle = FindFirstFile(dirName + @"\*", out findData);

        if( findHandle != INVALID_HANDLE_VALUE )
        {
            bool found;
            do
            {
                string currentFileName = findData.cFileName;

                if( ( (int)findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
                {
                    if( currentFileName != "." && currentFileName != ".." )
                    {
                        if( recursive )
                        {
                            List<string> childResults = FindFilesAndDirs(System.IO.Path.Combine(dirName, currentFileName), types, recursive);

                            subResults.AddRange(childResults);
                        }
                        if( ( types & FindType.Directories ) == FindType.Directories )
                        {
                            results.Add(System.IO.Path.Combine(dirName, currentFileName));
                        }
                    }
                }
                else
                {
                    if( ( types & FindType.Files ) == FindType.Files )
                    {
                        results.Add(System.IO.Path.Combine(dirName, currentFileName));
                    }
                }

                found = FindNextFile(findHandle, out findData);
            }
            while( found );
        }

        FindClose(findHandle);
        results.InsertRange(0, subResults);
        return results;
    }

    public static bool FileExists(string fileName)
    {
        System.IO.FileAttributes? x = AttributeHelper.GetAttributes(fileName);
        if( x != null )
            return ( ( (int)x & (int)System.IO.FileAttributes.Directory ) == 0 );

        return false;


        /*WIN32_FIND_DATA findData;
        IntPtr findHandle = IntPtr.Zero;
        try
        {
            findHandle = FindFirstFile(fileName, out findData);

            if (findHandle == INVALID_HANDLE_VALUE)
                return false;

            if (((int)findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
                return false;

            string currentFileName = findData.cFileName;
            if (fileName.EndsWith(currentFileName, StringComparison.InvariantCultureIgnoreCase))
                return true;

            return false;
        }
        finally
        {
            if (findHandle != IntPtr.Zero)
                FindClose(findHandle);
        }
        */
    }

    public static long FileLength(string fileName)
    {
        WIN32_FIND_DATA findData;
        IntPtr findHandle = IntPtr.Zero;
        try
        {
            findHandle = FindFirstFile(fileName, out findData);

            if( findHandle == INVALID_HANDLE_VALUE )
                return -1;

            if( ( (int)findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 )
                return -1;

            string currentFileName = findData.cFileName;
            if( fileName.EndsWith(currentFileName, StringComparison.InvariantCultureIgnoreCase) )
                return ( ( findData.nFileSizeHigh << 0x20 ) | ( findData.nFileSizeLow & ( (long)0xffffffffL ) ) );

            return -1;
        }
        finally
        {
            if( findHandle != IntPtr.Zero )
                FindClose(findHandle);
        }
    }

    public static bool DirectoryExists(string dirName)
    {
        System.IO.FileAttributes? x = AttributeHelper.GetAttributes(dirName);
        if( x != null )
            return ( ( (int)x & (int)System.IO.FileAttributes.Directory ) > 0 );

        return false;

        /*dirName = dirName.TrimEnd(new char[] { '\\' });

        WIN32_FIND_DATA findData;
        IntPtr findHandle = IntPtr.Zero;
        try
        {
            findHandle = FindFirstFile(dirName, out findData);

            if (findHandle == INVALID_HANDLE_VALUE)
                return false;

            if (((int)findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
                return false;

            string currentDirName = findData.cFileName;
            if (dirName.EndsWith(currentDirName, StringComparison.InvariantCultureIgnoreCase) || dirName.Length == 2)
                return true;

            return false;
        }
        finally
        {
            if (findHandle != IntPtr.Zero)
                FindClose(findHandle);
        }
         * */
    }

    internal static bool TypeAvailable()
    {
        return true;
    }

    internal static string LastError()
    {
        return new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()).Message;
    }

    internal static System.Security.Permissions.FileIOPermission GetFileIOPermission(System.Security.Permissions.FileIOPermissionAccess access, string path)
    {
        ConstructorInfo ci = typeof(System.Security.Permissions.FileIOPermission).GetConstructor(BindingFlags.NonPublic |
            BindingFlags.Instance, null, new Type[] { typeof(System.Security.Permissions.FileIOPermissionAccess), 
                                                   typeof(string[]), typeof(bool), typeof(bool) }, null);
        System.Security.Permissions.FileIOPermission fiop = (System.Security.Permissions.FileIOPermission)ci.Invoke(new object[] { access, new string[] { path }, false, false });
        return fiop;
    }

    public static string GetFullName(string path)
    {
        StringBuilder tmpFullName = new StringBuilder(LongPathSupport.MaxPath * 2 + 2);
        uint ret = GetFullPathName(path, MaxPath * 2 + 2, tmpFullName, null);
        if( ret > MaxPath || ret == 0 )
            return string.Empty;
        else
            return tmpFullName.ToString();
    }

    internal static bool CheckInvalidPathChars(string path)
    {
        int start = 0;
        if( path.StartsWith(@"\\?\") )
            start = 4;
        for( int i = start; i < path.Length; i++ )
        {
            int c = path[i];
            if( ( ( c == 0x22 ) || ( c == 60 ) ) || ( ( ( c == 0x3e ) || ( c == 0x7c ) ) || ( c < 0x20 ) ) )
            {
                return false;
            }
        }
        return true;
    }

    internal static bool IsDirectorySeparator(char c)
    {
        if( c != System.IO.Path.DirectorySeparatorChar )
        {
            return ( c == System.IO.Path.AltDirectorySeparatorChar );
        }
        return true;
    }

    internal static int GetRootLength(string path)
    {
        if( !CheckInvalidPathChars(path) )
            return -1;

        int mode = 0;
        int length = path.Length;
        if( ( length >= 1 ) && IsDirectorySeparator(path[0]) )
        {
            mode = 1;
            if( ( length >= 2 ) && IsDirectorySeparator(path[1]) )
            {
                mode = 2;
                int startIndex = 2;
                while( ( mode < length ) && ( !IsDirectorySeparator(path[mode]) || ( --startIndex > 0 ) ) )
                {
                    mode++;
                }
            }
            return mode;
        }
        if( ( length >= 2 ) && ( path[1] == System.IO.Path.VolumeSeparatorChar ) )
        {
            mode = 2;
            if( ( length >= 3 ) && IsDirectorySeparator(path[2]) )
            {
                mode++;
            }
        }
        return mode;
    }

    public static bool EncryptFile(string fileName)
    {
        throw new NotImplementedException("Development not finished for this method!");
        return EncryptFile(fileName);
    }

    public static bool DecryptFile(string fileName)
    {
        throw new NotImplementedException("Development not finished for this method!");
        return DecryptFile(fileName);
    }

    public static string GetDirectoryName(string path)
    {
        if( path != null )
        {
            if( !CheckInvalidPathChars(path) )
                return "?";

            path = GetFullName(path);
            int rootLength = GetRootLength(path);
            if( path.Length > rootLength )
            {
                int length = path.Length;
                if( length == rootLength )
                {
                    return null;
                }
                while( ( length > rootLength ) && !IsDirectorySeparator(path[--length]) )
                {
                }
                return path.Substring(0, length);
            }
        }
        return null;
    }


}



